/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Marcus Britanicus (https://gitlab.com/marcusbritanicus)
 * Copyright (c) 2021 Abrar (https://gitlab.com/s96Abrar)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 **/

#include <QDebug>
#include <QThread>
#include <QCoreApplication>

#include <wayqt/Idle.hpp>
#include <wayqt/Output.hpp>
#include <wayqt/WlrIdle.hpp>
#include <wayqt/Registry.hpp>
#include <wayqt/XdgShell.hpp>
#include <wayqt/DesQShell.hpp>
#include <wayqt/LayerShell.hpp>
#include <wayqt/ScreenCopy.hpp>
#include <wayqt/SessionLock.hpp>
#include <wayqt/DataControl.hpp>
#include <wayqt/GammaControl.hpp>
#include <wayqt/WayfireShell.hpp>
#include <wayqt/OutputManager.hpp>
#include <wayqt/WindowManager.hpp>
#include <wayqt/XdgActivation.hpp>
#include <wayqt/InputInhibition.hpp>
#include <wayqt/ToplevelManager.hpp>
#include <wayqt/OutputPowerManager.hpp>
#include <wayqt/ShortcutsInhibitor.hpp>

#include "idle-client-protocol.h"
#include "wayland-client-protocol.h"
#include "xdg-shell-client-protocol.h"
#include "xdg-activation-v1-client-protocol.h"
#include "ext-idle-notify-v1-client-protocol.h"
#include "ext-session-lock-v1-client-protocol.h"
#include "desq-shell-unstable-v1-client-protocol.h"
#include "wayfire-shell-unstable-v2-client-protocol.h"
#include "wlr-screencopy-unstable-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "wlr-data-control-unstable-v1-client-protocol.h"
#include "wlr-gamma-control-unstable-v1-client-protocol.h"
#include "wlr-input-inhibitor-unstable-v1-client-protocol.h"
#include "wlr-output-management-unstable-v1-client-protocol.h"
#include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
#include "wayfire-toplevel-management-unstable-v1-client-protocol.h"
#include "wlr-output-power-management-unstable-v1-client-protocol.h"
#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"

/* Convenience functions */
void WQt::Registry::globalAnnounce( void *data, struct wl_registry *, uint32_t name, const char *interface, uint32_t version ) {
    auto r = reinterpret_cast<WQt::Registry *>(data);

    r->handleAnnounce( name, interface, version );
}


void WQt::Registry::globalRemove( void *data, struct wl_registry *, uint32_t name ) {
    // who cares :D
    // but we will call WQt::Registry::handleRemove just for the heck of it

    auto r = reinterpret_cast<WQt::Registry *>(data);

    r->handleRemove( name );
}


const struct wl_registry_listener WQt::Registry::mRegListener = {
    globalAnnounce,
    globalRemove,
};

WQt::Registry::Registry( wl_display *wlDisplay ) {
    mWlDisplay = wlDisplay;
    mObj       = wl_display_get_registry( mWlDisplay );

    if ( wl_proxy_get_listener( (wl_proxy *)mObj ) != &mRegListener ) {
        wl_registry_add_listener( mObj, &mRegListener, this );
    }

    wl_display_roundtrip( mWlDisplay );
}


WQt::Registry::~Registry() {
    wl_registry_destroy( mObj );

    wl_seat_destroy( mWlSeat );
    wl_shm_destroy( mWlShm );

    for ( WQt::Output *op: mOutputs ) {
        delete op;
    }

    if ( mXdgShell != nullptr ) {
        delete mXdgShell;
    }

    if ( mLayerShell != nullptr ) {
        delete mLayerShell;
    }

    if ( mWayfireShell != nullptr ) {
        delete mWayfireShell;
    }

    if ( mDesQShell != nullptr ) {
        delete mDesQShell;
    }

    if ( mInhibitManager != nullptr ) {
        delete mInhibitManager;
    }

    if ( mWindowMgr != nullptr ) {
        delete mWindowMgr;
    }

    if ( mToplevelMgr != nullptr ) {
        delete mToplevelMgr;
    }

    if ( mScreenCopyMgr != nullptr ) {
        delete mScreenCopyMgr;
    }

    if ( mDataControlMgr != nullptr ) {
        delete mDataControlMgr;
    }

    if ( mOutputPowerMgr != nullptr ) {
        delete mOutputPowerMgr;
    }

    if ( mOutputMgr != nullptr ) {
        delete mOutputMgr;
    }

    if ( mIdleMgr != nullptr ) {
        delete mIdleMgr;
    }

    if ( mWlrIdleMgr != nullptr ) {
        delete mWlrIdleMgr;
    }

    if ( mGammaCtrl != nullptr ) {
        delete mGammaCtrl;
    }

    if ( mSessLockMgr != nullptr ) {
        delete mSessLockMgr;
    }

    if ( mXdgActivation != nullptr ) {
        delete mXdgActivation;
    }
}


void WQt::Registry::setup() {
    if ( mIsSetup == false ) {
        mIsSetup = true;

        for ( WQt::Registry::ErrorType et: pendingErrors ) {
            emit errorOccured( et );
        }

        for ( WQt::Registry::Interface iface: pendingInterfaces ) {
            emit interfaceRegistered( iface );
        }

        for ( WQt::Output *op: pendingOutputs ) {
            emit outputAdded( op );
        }
    }
}


wl_registry *WQt::Registry::get() {
    return mObj;
}


wl_display *WQt::Registry::waylandDisplay() {
    return mWlDisplay;
}


wl_seat *WQt::Registry::waylandSeat() {
    return mWlSeat;
}


wl_shm *WQt::Registry::waylandShm() {
    return mWlShm;
}


QList<WQt::Output *> WQt::Registry::waylandOutputs() {
    return mOutputs.values();
}


QList<uint32_t> WQt::Registry::registeredInterfaces() {
    return mRegisteredInterfaces;
}


bool WQt::Registry::waitForInterface( WQt::Registry::Interface id, int timeout ) {
    int t = 0;

    while ( t < timeout ) {
        if ( mRegisteredInterfaces.contains( id ) ) {
            return true;
        }

        /** We will take 10 ms nap */
        QThread::msleep( 10 );

        /** Increment the timer */
        t += 10;

        /** Flush the queue */
        QCoreApplication::processEvents();
    }

    return false;
}


WQt::XdgShell *WQt::Registry::xdgShell() {
    return mXdgShell;
}


WQt::LayerShell *WQt::Registry::layerShell() {
    return mLayerShell;
}


WQt::Shell *WQt::Registry::wayfireShell() {
    return mWayfireShell;
}


WQt::DesQShell *WQt::Registry::desqShell() {
    return mDesQShell;
}


WQt::WindowManager *WQt::Registry::windowManager() {
    return mWindowMgr;
}


WQt::ToplevelManager *WQt::Registry::toplevelManager() {
    return mToplevelMgr;
}


WQt::InputInhibitManager *WQt::Registry::inputInhibitManager() {
    return mInhibitManager;
}


WQt::ShortcutsInhibitManager *WQt::Registry::shortcutsInhibitor() {
    return mShortcutsInhibitor;
}


WQt::ScreenCopyManager *WQt::Registry::screenCopier() {
    return mScreenCopyMgr;
}


WQt::DataControlManager *WQt::Registry::dataControlManager() {
    return mDataControlMgr;
}


WQt::OutputPowerManager *WQt::Registry::outputPowerManager() {
    return mOutputPowerMgr;
}


WQt::OutputManager *WQt::Registry::outputManager() {
    return mOutputMgr;
}


WQt::SessionLockManager *WQt::Registry::sessionLockManager() {
    return mSessLockMgr;
}


WQt::IdleManager *WQt::Registry::idleManager() {
    return mWlrIdleMgr;
}


WQt::IdleNotifier *WQt::Registry::idleNotifier() {
    return mIdleMgr;
}


WQt::GammaControlManager *WQt::Registry::gammaControlManager() {
    return mGammaCtrl;
}


WQt::XdgActivation *WQt::Registry::xdgActivation() {
    return mXdgActivation;
}


void WQt::Registry::handleAnnounce( uint32_t name, const char *interface, uint32_t version ) {
    /**
     * We really don't care about wl_seat version right now.
     */
    if ( strcmp( interface, wl_seat_interface.name ) == 0 ) {
        mWlSeat = (wl_seat *)wl_registry_bind( mObj, name, &wl_seat_interface, version );

        if ( !mWlSeat ) {
            emitError( WQt::Registry::EmptySeat );
        }
    }

    /**
     * We really don't care about wl_shm version right now.
     */
    if ( strcmp( interface, wl_shm_interface.name ) == 0 ) {
        mWlShm = (wl_shm *)wl_registry_bind( mObj, name, &wl_shm_interface, version );

        if ( !mWlShm ) {
            emitError( WQt::Registry::EmptyShm );
        }

        else {
            mRegisteredInterfaces << ShmInterface;
            emitInterface( ShmInterface, true );
        }
    }

    /**
     * We really don't care about wl_output version right now.
     */
    if ( strcmp( interface, wl_output_interface.name ) == 0 ) {
        wl_output *op = (wl_output *)wl_registry_bind( mObj, name, &wl_output_interface, version );

        if ( op ) {
            mOutputs[ name ] = new WQt::Output( op );
            emitOutput( mOutputs[ name ], true );
        }
    }

    /**
     * We really don't care about xdg_wm_base version right now.
     */
    else if ( strcmp( interface, xdg_wm_base_interface.name ) == 0 ) {
        mXdgWmBase = (xdg_wm_base *)wl_registry_bind( mObj, name, &xdg_wm_base_interface, version );

        if ( !mXdgWmBase ) {
            emitError( WQt::Registry::EmptyXdgWmBase );
        }

        else {
            mXdgShell = new WQt::XdgShell( mXdgWmBase );

            mRegisteredInterfaces << XdgWmBaseInterface;
            emitInterface( XdgWmBaseInterface, true );
        }
    }

    /**
     * Wlroots 0.13.0 introduced support for yes/no/maybe.
     */
    else if ( strcmp( interface, zwlr_layer_shell_v1_interface.name ) == 0 ) {
        mWlrLayerShell = (zwlr_layer_shell_v1 *)wl_registry_bind( mObj, name, &zwlr_layer_shell_v1_interface, version );

        if ( !mWlrLayerShell ) {
            emitError( WQt::Registry::EmptyLayerShell );
        }

        else {
            mLayerShell = new WQt::LayerShell( mWlrLayerShell, version );

            mRegisteredInterfaces << LayerShellInterface;
            emitInterface( LayerShellInterface, true );
        }
    }

    /**
     * We're happy with what's available in version 1.
     */
    else if ( strcmp( interface, zwlr_input_inhibit_manager_v1_interface.name ) == 0 ) {
        mWlrInhibitMgr = (zwlr_input_inhibit_manager_v1 *)wl_registry_bind( mObj, name, &zwlr_input_inhibit_manager_v1_interface, 1 );

        if ( !mWlrInhibitMgr ) {
            emitError( WQt::Registry::EmptyInputInhibitManager );
        }

        else {
            mInhibitManager = new WQt::InputInhibitManager( mWlrInhibitMgr );

            mRegisteredInterfaces << InputInhibitManagerInterface;
            emitInterface( InputInhibitManagerInterface, true );
        }
    }

    /**
     * We're happy with what's available in version 1.
     */
    else if ( strcmp( interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name ) == 0 ) {
        mWlShortcutsInhibitor = (zwp_keyboard_shortcuts_inhibit_manager_v1 *)wl_registry_bind( mObj, name, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1 );

        if ( !mWlShortcutsInhibitor ) {
            emitError( WQt::Registry::EmptyShortcutsInhibitor );
        }

        else {
            mShortcutsInhibitor = new WQt::ShortcutsInhibitManager( mWlShortcutsInhibitor );

            mRegisteredInterfaces << ShortcutsInhibitorInterface;
            emitInterface( ShortcutsInhibitorInterface, true );
        }
    }

    /**
     * We're happy with what's available in version 3.
     */
    else if ( strcmp( interface, zwlr_foreign_toplevel_manager_v1_interface.name ) == 0 ) {
        mWlrWindowMgr = (zwlr_foreign_toplevel_manager_v1 *)wl_registry_bind( mObj, name, &zwlr_foreign_toplevel_manager_v1_interface, 3 );

        if ( !mWlrWindowMgr ) {
            emitError( WQt::Registry::EmptyWindowManager );
        }

        else {
            mWindowMgr = new WQt::WindowManager( mWlrWindowMgr );

            mRegisteredInterfaces << WindowManagerInterface;
            emitInterface( WindowManagerInterface, true );
        }
    }

    /**
     * We're happy with what's available in version 1.
     */
    else if ( strcmp( interface, zwayfire_toplevel_manager_v1_interface.name ) == 0 ) {
        mWfToplevelMgr = (zwayfire_toplevel_manager_v1 *)wl_registry_bind( mObj, name, &zwayfire_toplevel_manager_v1_interface, 1 );

        if ( !mWfToplevelMgr ) {
            emitError( WQt::Registry::EmptyToplevelManager );
        }

        else {
            mToplevelMgr = new WQt::ToplevelManager( mWfToplevelMgr );

            mRegisteredInterfaces << ToplevelManagerInterface;
            emitInterface( ToplevelManagerInterface, true );
        }
    }

    /**
     * We're happy with what's available in version 1.
     */
    else if ( strcmp( interface, zwf_shell_manager_v2_interface.name ) == 0 ) {
        mWfShellMgr = (zwf_shell_manager_v2 *)wl_registry_bind( mObj, name, &zwf_shell_manager_v2_interface, 1 );

        if ( !mWfShellMgr ) {
            emitError( WQt::Registry::EmptyWayfireShell );
        }

        else {
            mWayfireShell = new WQt::Shell( mWfShellMgr );

            mRegisteredInterfaces << WayfireShellInterface;
            emitInterface( WayfireShellInterface, true );
        }
    }

    /**
     * We're happy with what's available in version 1.
     */
    else if ( strcmp( interface, zdesq_shell_manager_v1_interface.name ) == 0 ) {
        mDesQShellMgr = (zdesq_shell_manager_v1 *)wl_registry_bind( mObj, name, &zdesq_shell_manager_v1_interface, 1 );

        if ( !mDesQShellMgr ) {
            emitError( WQt::Registry::EmptyDesQShell );
        }

        else {
            mDesQShell = new WQt::DesQShell( mDesQShellMgr );

            mRegisteredInterfaces << DesQShellInterface;
            emitInterface( DesQShellInterface, true );
        }
    }

    /**
     * We're happy with what's available in version 1. (KDE Idle)
     */
    else if ( strcmp( interface, org_kde_kwin_idle_interface.name ) == 0 ) {
        mKdeIdle = (org_kde_kwin_idle *)wl_registry_bind( mObj, name, &org_kde_kwin_idle_interface, 1 );

        if ( !mKdeIdle ) {
            emitError( WQt::Registry::EmptyWlrIdle );
        }

        else {
            mWlrIdleMgr = new WQt::IdleManager( mKdeIdle );

            mRegisteredInterfaces << WlrIdleInterface;
            emitInterface( WlrIdleInterface, true );
        }
    }

    /**
     * We're happy with what's available in version 1. (Wayland Idle)
     */
    else if ( strcmp( interface, ext_idle_notifier_v1_interface.name ) == 0 ) {
        mIdle = (ext_idle_notifier_v1 *)wl_registry_bind( mObj, name, &ext_idle_notifier_v1_interface, 1 );

        if ( !mIdle ) {
            emitError( WQt::Registry::EmptyIdle );
        }

        else {
            mIdleMgr = new WQt::IdleNotifier( mIdle );

            mRegisteredInterfaces << IdleInterface;
            emitInterface( IdleInterface, true );
        }
    }

    /**
     * We've implemented version 3.
     * And wlroots 0.15.0 has version 3 available.
     */
    else if ( strcmp( interface, zwlr_screencopy_manager_v1_interface.name ) == 0 ) {
        mWlrScreenCopyMgr = (zwlr_screencopy_manager_v1 *)wl_registry_bind( mObj, name, &zwlr_screencopy_manager_v1_interface, 3 );

        if ( !mWlrScreenCopyMgr ) {
            emitError( WQt::Registry::EmptyScreenCopyManager );
        }

        else {
            mScreenCopyMgr = new WQt::ScreenCopyManager( mWlrScreenCopyMgr );

            mRegisteredInterfaces << ScreenCopyManagerInterface;
            emitInterface( ScreenCopyManagerInterface, true );
        }
    }

    /**
     * We've implemented version 2.
     * And wlroots 0.15.0 has version 2 available.
     */
    else if ( strcmp( interface, zwlr_data_control_manager_v1_interface.name ) == 0 ) {
        mWlrDataControlMgr = (zwlr_data_control_manager_v1 *)wl_registry_bind( mObj, name, &zwlr_data_control_manager_v1_interface, 2 );

        if ( !mWlrDataControlMgr ) {
            emitError( WQt::Registry::EmptyDataControlManager );
        }

        else {
            mDataControlMgr = new WQt::DataControlManager( mWlrDataControlMgr );

            mRegisteredInterfaces << DataControlManagerInterface;
            emitInterface( DataControlManagerInterface, true );
        }
    }

    /**
     * We've implemented version 1.
     * And wlroots 0.15.0 has version 1 available.
     */
    else if ( strcmp( interface, zwlr_output_power_manager_v1_interface.name ) == 0 ) {
        mWlrOutputPowerMgr = (zwlr_output_power_manager_v1 *)wl_registry_bind( mObj, name, &zwlr_output_power_manager_v1_interface, 1 );

        if ( !mWlrOutputPowerMgr ) {
            emitError( WQt::Registry::EmptyOutputPowerManager );
        }

        else {
            mOutputPowerMgr = new WQt::OutputPowerManager( mWlrOutputPowerMgr );

            mRegisteredInterfaces << OutputManagerInterface;
            emitInterface( OutputManagerInterface, true );
        }
    }

    /**
     * We've implemented version 2.
     * And wlroots 0.15.0 has version 2 available.
     */
    else if ( strcmp( interface, zwlr_output_manager_v1_interface.name ) == 0 ) {
        mWlrOutputMgr = (zwlr_output_manager_v1 *)wl_registry_bind( mObj, name, &zwlr_output_manager_v1_interface, 2 );

        if ( !mWlrOutputMgr ) {
            emitError( WQt::Registry::EmptyOutputManager );
        }

        else {
            mOutputMgr = new WQt::OutputManager( mWlrOutputMgr );

            mRegisteredInterfaces << OutputManagerInterface;
            emitInterface( OutputManagerInterface, true );
        }
    }

    /**
     * We've implemented version 1.
     * And wlroots 0.15.0 has version 1 available.
     */
    else if ( strcmp( interface, ext_session_lock_manager_v1_interface.name ) == 0 ) {
        mExtSessLockMgr = (ext_session_lock_manager_v1 *)wl_registry_bind( mObj, name, &ext_session_lock_manager_v1_interface, 1 );

        if ( !mExtSessLockMgr ) {
            emitError( WQt::Registry::EmptySessionLockManager );
        }

        else {
            mSessLockMgr = new WQt::SessionLockManager( mExtSessLockMgr );

            mRegisteredInterfaces << SessionLockManagerInterface;
            emitInterface( SessionLockManagerInterface, true );
        }
    }

    /**
     * We've implemented version 1.
     * And wlroots 0.15.0 has version 1 available.
     */
    else if ( strcmp( interface, zwlr_gamma_control_manager_v1_interface.name ) == 0 ) {
        mWlrGammaCtrl = (zwlr_gamma_control_manager_v1 *)wl_registry_bind( mObj, name, &zwlr_gamma_control_manager_v1_interface, 1 );

        if ( !mWlrGammaCtrl ) {
            emitError( WQt::Registry::EmptyGammaControlManager );
        }

        else {
            mGammaCtrl = new WQt::GammaControlManager( mWlrGammaCtrl );

            mRegisteredInterfaces << GammaControlManagerInterface;
            emitInterface( GammaControlManagerInterface, true );
        }
    }

    /**
     * We've implemented version 1.
     * And wlroots 0.15.0 has version 1 available.
     */
    else if ( strcmp( interface, xdg_activation_v1_interface.name ) == 0 ) {
        mWlXdgActivation = (xdg_activation_v1 *)wl_registry_bind( mObj, name, &xdg_activation_v1_interface, 1 );

        if ( !mWlXdgActivation ) {
            emitError( WQt::Registry::EmptyXdgActivation );
        }

        else {
            mXdgActivation = new WQt::XdgActivation( mWlXdgActivation );

            mRegisteredInterfaces << XdgActivationInterface;
            emitInterface( XdgActivationInterface, true );
        }
    }
}


void WQt::Registry::handleRemove( uint32_t name ) {
    /**
     * While we do not care about most of the handleRemove,
     * we need to worry about the wl_output * objects.
     */
    if ( mOutputs.keys().contains( name ) ) {
        WQt::Output *output = mOutputs.take( name );
        emitOutput( output, false );
    }
}


void WQt::Registry::emitError( ErrorType et ) {
    if ( mIsSetup ) {
        emit errorOccured( et );
    }

    else {
        pendingErrors << et;
    }
}


void WQt::Registry::emitOutput( WQt::Output *op, bool added ) {
    if ( mIsSetup ) {
        if ( added ) {
            emit outputAdded( op );
        }

        else {
            emit outputRemoved( op );
        }
    }

    else {
        if ( added ) {
            pendingOutputs << op;
        }

        else {
            pendingOutputs.removeAll( op );
        }
    }
}


void WQt::Registry::emitInterface( WQt::Registry::Interface iface, bool added ) {
    if ( mIsSetup ) {
        if ( added ) {
            emit interfaceRegistered( iface );
        }

        else {
            emit interfaceDeregistered( iface );
        }
    }

    else {
        if ( added ) {
            pendingInterfaces << iface;
        }

        else {
            pendingInterfaces.removeAll( iface );
        }
    }
}
